/*
 * Copyright 2008 Neil Bacon
 * Distributed under the terms of the Mozilla Public License version 1.1
 * or the GNU General Public License version 3.
 */

package nb.xadd.operator.impl;

import java.util.BitSet;

import nb.cache.CacheMap;
import nb.xadd.node.Node;
import nb.xadd.node.NodeFactory;
import nb.xadd.node.impl.BooleanDecisionNode;
import nb.xadd.node.impl.RealEqualDecisionNode;
import nb.xadd.node.impl.RealGreaterOrEqualDecisionNode;
import nb.xadd.operator.UnaryOperator;

/**
 * Negate tests on the specified variables (swap true and false children).
 */
public final class Negation implements UnaryOperator {

//    private Log log = LogFactory.getLog(getClass());

    private final NodeFactory nodeFactory;
    private final CacheMap<UnaryOperatorCacheKey, Node> unaryOperatorCache;
    private final BitSet booleanVarIndexSet;
    private final BitSet realVarIndexSet;
    private final int hashCode;

    protected Negation(NodeFactory nodeFactory,
            CacheMap<UnaryOperatorCacheKey, Node> unaryOperatorCache,
            BitSet booleanVarIndexSet, BitSet realVarIndexSet) {
        super();
        this.nodeFactory = nodeFactory;
        this.unaryOperatorCache = unaryOperatorCache;
        this.booleanVarIndexSet = booleanVarIndexSet;
        this.realVarIndexSet = realVarIndexSet;
        hashCode = calcHashCode();
    }

    public Node execute(Node n) {
        UnaryOperatorCacheKey key = new UnaryOperatorCacheKey(this, n);
        Node result = unaryOperatorCache.get(key);
        if (result == null) {
            result = walk(n);
            unaryOperatorCache.put(key, result);
        }
        return result;
    }

    private Node walk(Node n) {
        Node result;
        if (n instanceof RealGreaterOrEqualDecisionNode) {
            RealGreaterOrEqualDecisionNode dn = (RealGreaterOrEqualDecisionNode) n;
            Node trueChild = walk(dn.getTrueChild());
            Node falseChild = walk(dn.getFalseChild());
            result = realVarIndexSet.get(dn.getRealVarIndex())
                ? nodeFactory.getRealGreaterOrEqualDecisionNode(dn.getRealVarIndex(), dn.getTestValue(), falseChild, trueChild)
                : nodeFactory.getRealGreaterOrEqualDecisionNode(dn.getRealVarIndex(), dn.getTestValue(), trueChild, falseChild);
        } else if (n instanceof RealEqualDecisionNode) {
            RealEqualDecisionNode dn = (RealEqualDecisionNode) n;
            Node trueChild = walk(dn.getTrueChild());
            Node falseChild = walk(dn.getFalseChild());
            result = realVarIndexSet.get(dn.getRealVarIndex())
                ? nodeFactory.getRealEqualDecisionNode(dn.getRealVarIndex(), dn.getTestValue(), falseChild, trueChild)
                : nodeFactory.getRealEqualDecisionNode(dn.getRealVarIndex(), dn.getTestValue(), trueChild, falseChild);
        } else if (n instanceof BooleanDecisionNode) {
            BooleanDecisionNode dn = (BooleanDecisionNode) n;
            Node trueChild = walk(dn.getTrueChild());
            Node falseChild = walk(dn.getFalseChild());
            result = booleanVarIndexSet.get(dn.getBooleanVarIndex())
                ? nodeFactory.getBooleanDecisionNode(dn.getBooleanVarIndex(), falseChild, trueChild)
                : nodeFactory.getBooleanDecisionNode(dn.getBooleanVarIndex(), trueChild, falseChild);
        } else {
            result = n;
        }
//        if (log.isDebugEnabled()) {
//            log.debug("walk: result = " + result);
//        }
        return result;
    }

    @Override
    public int hashCode() {
        return hashCode;
    }

    protected int calcHashCode() {
        final int prime = 197;
        int result = 1;
        result = prime * result + realVarIndexSet.hashCode();
        result = prime * result + booleanVarIndexSet.hashCode();
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null || getClass() != obj.getClass())
            return false;
        Negation other = (Negation) obj;
        return booleanVarIndexSet.equals(other.booleanVarIndexSet) && realVarIndexSet.equals(other.realVarIndexSet);
    }

}
